<?php
namespace JSSDK;

use JSSDK\Config\URLConfig;
use JSSDK\Http\RequestCore;
use JSSDK\Http\RequestCore_Exception;
use JSSDK\Http\ResponseCore;
use JSSDK\Core\JSSDKException;
use JSSDK\Core\JSSDKUtil;
use JSSDK\Model\YFUserInfo;
use JSSDK\Model\YFAccessToken;
use JSSDK\Result\UserInfoResult;
use JSSDK\Result\AccessTokenResult;


/**
 * Class JSSDKClient
 *
 * JSSDK 客户端类，封装了用户通过 API 对 UCenter 服务的各种操作，
 * 用户通过 JSSDKClient 实例可以进行 获取用户信息、头像等操作，具体
 * 的接口规则可以参考官方 JSSDK API文档
 */
class JSSDKClient
{
    /**
     * 构造函数
     *
     * 构造函数有几种情况：
     * 1. 一般的时候初始化使用 $JSSDKClient = new JSSDKClient($appid, $redirect_url)
     *
     * @param string $appid 从 用户中心 获得的 应用 api key
     * @param string $secret 从 用户中心 获得的 应用 secret
     * @param string $redirect_url 授权后回调的地址
     * @throws JSSDKException
     */
    public function __construct($appid,$secret, $redirect_url = "")
    {
        $appid = trim($appid);
        $secret = trim($secret);
        $redirect_url = trim($redirect_url);

        if (empty($appid)) {
            throw new JSSDKException("appid key id is empty");
        }
        if (empty($secret)) {
            throw new JSSDKException("secret key id is empty");
        }
        $this->hostname = URLConfig::URL_HOST;
        $this->appid = $appid;
        $this->secret = $secret;
        $this->redirect_url = $redirect_url;
        self::checkEnv();
    }

    /**
     * 获取用户信息
     *
     * @throws JSSDKException
     */
    public function getUserInfo(){
        $userInfo = YFUserInfo::getUser();
        if($userInfo){
            return $userInfo;
        }else{
            $yf_userInfo = JSSDKUtil::fetch_user_info();
            if($yf_userInfo){
                return $yf_userInfo;
            }
            if(isset($_GET['code'])){
                return $this->fetch_user_info($_GET['code']);
            }else{
                $this->fetch_code();
            }
        }
    }

    private function fetch_code(){
        // TODO 获取授权 code
        if (empty($this->appid)) {
            throw new JSSDKException("appid key id is empty");
        }

        $redirect_url = $this->redirect_url;
        if(!$redirect_url){
            $redirect_url = JSSDKUtil::getCurrentURL();
        }
        // 重定向获取授权
        $scheme = $this->useSSL ? 'https://' : 'http://';
        $oauth2_url = $scheme . URLConfig::url_authorize()."?appid=" . $this->appid . "&redirect_url=" . urlencode($redirect_url) . "&response_type=code&scope=snsapi_base&state=jssdk_ucenter#yangfan_redirect";
        header('HTTP/1.1 302 Found');
        header("location: $oauth2_url");

        // 不可 ajax 请求
//        $options['url'] = URLConfig::url_authorize();
//        $content['appid'] = $this->appid;
//        $content['redirect_url'] = JSSDKUtil::getCurrentURL();
//        $content['response_type'] = 'code';
//        $content['state'] = '1';
//        $content['scope'] = 'snsapi_base';
//        $options[self::JSSDK_CONTENT] = $content;
//        $response = $this->httpRequest($options);
//        $result = new UserInfoResult($response);
//        return $result->getData();
    }

    private function fetch_access_token($code){
        if (empty($this->appid)) {
            throw new JSSDKException("appid key id is empty");
        }
        if (empty($this->secret)) {
            throw new JSSDKException("secret key id is empty");
        }

        $options['url'] = URLConfig::url_access_token();
        $options['grant_type'] = 'authorization_code';
        $content['appid'] = $this->appid;
        $content['secret'] = $this->secret;
        $content['code'] = $code;
        $options[self::JSSDK_QUERY_STRING] = $content;
//        $options[self::JSSDK_CONTENT] = $content;// 往body里面放入参数
        $response = $this->httpRequest($options);
        $result = new AccessTokenResult($response);
        return $result->getData();
    }

    private function fetch_user_info($code){
        // 获取 access_token
        $access_token = $this->fetch_access_token($code);

        if($access_token['access_token']){
            $options['url'] = URLConfig::url_user_info();
            $content['access_token'] = $access_token['access_token'];
            $content['openid'] =$access_token['openid'];
            $options[self::JSSDK_QUERY_STRING] = $content;
//        $options[self::JSSDK_CONTENT] = $content; // 往body里面放入参数
            $response = $this->httpRequest($options);
            $result = new UserInfoResult($response);
            return $result->getData();
        }

        return $access_token;
    }

    /**
     * 发送 http 请求，返回数据
     * @param array $options
     * @return ResponseCore
     * @throws JSSDKException
     * @throws RequestCore_Exception
     */
    private function httpRequest($options){

        $scheme = $this->useSSL ? 'https://' : 'http://';
        // 获得当次请求使用的hostname，如果是公共域名或者专有域名，bucket拼在前面构成三级域名
        $hostname = $options['url'];
        $headers = $this->generateHeaders($options);
//        $signable_query_string_params = $this->generateSignableQueryStringParam($options);
//        $signable_query_string = JSSDKUtil::toQueryString($options[self::JSSDK_QUERY_STRING]);
//        $resource_uri = $this->generateResourceUri($options);
        //生成请求URL
        $conjunction = '?';
        $non_signable_resource = '';
//        if (isset($options[self::JSSDK_SUB_RESOURCE])) {
//            $conjunction = '&';
//        }
//        if ($signable_query_string !== '') {
//            $signable_query_string = $conjunction . $signable_query_string;
//            $conjunction = '&';
//        }
        $query_string = $this->generateQueryString($options);
        if ($query_string !== '') {
            $non_signable_resource .= $conjunction . $query_string;
            $conjunction = '&';
        }
//        $this->requestUrl = $scheme . $hostname . $resource_uri . $signable_query_string . $non_signable_resource;
        $this->requestUrl = $scheme . $hostname . $non_signable_resource;

        //创建请求
        $request = new RequestCore($this->requestUrl);

        if (isset($options[self::JSSDK_METHOD])) {
            $request->set_method($options[self::JSSDK_METHOD]);
        }else{
            $request->set_method(self::JSSDK_HTTP_POST);
        }

        if (isset($options[self::JSSDK_CONTENT])) {
            $request->set_body($options[self::JSSDK_CONTENT]);
            if ($headers[self::JSSDK_CONTENT_TYPE] === 'application/x-www-form-urlencoded') {
                $headers[self::JSSDK_CONTENT_TYPE] = 'application/octet-stream';
            }
            $headers[self::JSSDK_CONTENT_LENGTH] = strlen(json_encode($options[self::JSSDK_CONTENT]));
        }

        foreach ($headers as $header_key => $header_value) {
            $header_value = str_replace(array("\r", "\n"), '', $header_value);
            if ($header_value !== '' || $header_key === self::JSSDK_ACCEPT_ENCODING) {
                $request->add_header($header_key, $header_value);
            }
        }

        if ($this->timeout !== 0) {
            $request->timeout = $this->timeout;
        }
        if ($this->connectTimeout !== 0) {
            $request->connect_timeout = $this->connectTimeout;
        }
        try {
            $request->send_request();
        } catch (RequestCore_Exception $e) {
            throw(new JSSDKException('RequestCoreException: ' . $e->getMessage()));
        }
        $response_header = $request->get_response_header();
        $response_header['jssdk-request-url'] = $this->requestUrl;
        $response_header['jssdk-redirects'] = $this->redirects;
        $response_header['jssdk-requestheaders'] = $request->request_headers;

        $data = new ResponseCore($response_header, $request->get_response_body(), $request->get_response_code());
        //retry if JSSDK Internal Error
        if ((integer)$request->get_response_code() === 500) {
            if ($this->redirects <= $this->maxRetries) {
                //设置休眠
                $delay = (integer)(pow(4, $this->redirects) * 100000);
                usleep($delay);
                $this->redirects++;
                $data = $this->httpRequest($options);
            }
        }

        $this->redirects = 0;
        return $data;
    }

    /**
     * 生成query_string
     *
     * @param mixed $options
     * @return string
     */
    private function generateQueryString($options)
    {
        //请求参数
        $queryStringParams = array();
        if (isset($options[self::JSSDK_QUERY_STRING])) {
            $queryStringParams = array_merge($queryStringParams, $options[self::JSSDK_QUERY_STRING]);
        }
        return JSSDKUtil::toQueryString($queryStringParams);
    }

    /**
     * 获得当次请求的资源定位字段
     *
     * @param $options
     * @return string 资源定位字段
     */
    private function generateResourceUri($options)
    {
        $resource_uri = "";

        // resource_uri + sub_resource
        $conjunction = '?';
        if (isset($options[self::JSSDK_SUB_RESOURCE])) {
            $resource_uri .= $conjunction . $options[self::JSSDK_SUB_RESOURCE];
        }
        return $resource_uri;
    }

    /**
     * 生成signalbe_query_string_param, array类型
     *
     * @param array $options
     * @return array
     */
    private function generateSignableQueryStringParam($options)
    {
        $signableQueryStringParams = array();
        $signableList = array(
            'response-content-type',
            'response-content-language',
            'response-cache-control',
            'response-content-encoding',
            'response-expires',
            'response-content-disposition',
        );

        foreach ($signableList as $item) {
            if (isset($options[$item])) {
                $signableQueryStringParams[$item] = $options[$item];
            }
        }

        return $signableQueryStringParams;
    }

    /**
     * 初始化headers
     *
     * @param mixed $options
     * @return array
     */
    private function generateHeaders($options)
    {
        $headers = array(
            self::JSSDK_CONTENT_TYPE => isset($options[self::JSSDK_CONTENT_TYPE]) ? $options[self::JSSDK_CONTENT_TYPE] : self::DEFAULT_CONTENT_TYPE,
        );
        //合并HTTP headers
        if (isset($options[self::JSSDK_HEADERS])) {
            $headers = array_merge($headers, $options[self::JSSDK_HEADERS]);
        }
        return $headers;
    }

    /**
     * 设置最大尝试次数
     *
     * @param int $maxRetries
     * @return void
     */
    public function setMaxTries($maxRetries = 3)
    {
        $this->maxRetries = $maxRetries;
    }

    /**
     * 获取最大尝试次数
     *
     * @return int
     */
    public function getMaxRetries()
    {
        return $this->maxRetries;
    }

    /**
     * 用来检查sdk所以来的扩展是否打开
     *
     * @throws JSSDKException
     */
    public static function checkEnv()
    {
        if (function_exists('get_loaded_extensions')) {
            //检测curl扩展
            $enabled_extension = array("curl");
            $extensions = get_loaded_extensions();
            if ($extensions) {
                foreach ($enabled_extension as $item) {
                    if (!in_array($item, $extensions)) {
                        throw new JSSDKException("Extension {" . $item . "} is not installed or not enabled, please check your php env.");
                    }
                }
            } else {
                throw new JSSDKException("function get_loaded_extensions not found.");
            }
        } else {
            throw new JSSDKException('Function get_loaded_extensions has been disabled, please check php config.');
        }
    }

    /**
     //* 设置http库的请求超时时间，单位秒
     *
     * @param int $timeout
     */
    public function setTimeout($timeout)
    {
        $this->timeout = $timeout;
    }

    /**
     * 设置http库的连接超时时间，单位秒
     *
     * @param int $connectTimeout
     */
    public function setConnectTimeout($connectTimeout)
    {
        $this->connectTimeout = $connectTimeout;
    }


    // JSSDK 内部常量
    const JSSDK_HEADERS = JSSDKUtil::JSSDK_HEADERS;
    const JSSDK_METHOD = 'method';
    const JSSDK_CONTENT = JSSDKUtil::JSSDK_CONTENT;
    const JSSDK_BODY = 'body';
    const JSSDK_LENGTH = JSSDKUtil::JSSDK_LENGTH;
    const JSSDK_ACCEPT_ENCODING = 'Accept-Encoding';
    const JSSDK_CONTENT_TYPE = 'Content-Type';
    const JSSDK_CONTENT_LENGTH = 'Content-Length';
    const DEFAULT_CONTENT_TYPE = 'application/octet-stream';// application/octet-stream application/x-www-form-urlencoded
    const JSSDK_QUERY_STRING = 'query_string';
    const JSSDK_SUB_RESOURCE = 'sub_resource';

    //HTTP方法
    const JSSDK_HTTP_GET = 'GET';
    const JSSDK_HTTP_PUT = 'PUT';
    const JSSDK_HTTP_HEAD = 'HEAD';
    const JSSDK_HTTP_POST = 'POST';
    const JSSDK_HTTP_DELETE = 'DELETE';
    const JSSDK_HTTP_OPTIONS = 'OPTIONS';

    // JSSDKClient版本信息
    const JSSDK_NAME = "netuo-sdk-php";
    const JSSDK_VERSION = "0.0.1";
    const JSSDK_BUILD = "20171108";
    const JSSDK_AUTHOR = "JSSDK";
    const JSSDK_OPTIONS_ORIGIN = 'Origin';
    const JSSDK_OPTIONS_REQUEST_METHOD = 'Access-Control-Request-Method';
    const JSSDK_OPTIONS_REQUEST_HEADERS = 'Access-Control-Request-Headers';

    //是否使用ssl
    private $useSSL = false;
    private $maxRetries = 3;
    private $redirects = 0;

    private $requestUrl;
    private $appid;// 应用 api key
    private $secret;// 应用 secret
    private $redirect_url; // 授权回调地址
    private $hostname;
    private $timeout = 0;
    private $connectTimeout = 0;
}
